/* $ZEL: sis1100_ddma_startstop.c,v 1.2 2006/03/10 12:23:49 wuestner Exp $ */

/*
 * Copyright (c) 2005
 * 	Peter Wuestner.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "sis1100_sc.h"

/* start_dma may be called from interrupt! */
static void
start_dma(struct sis1100_softc *sc, int block)
{
    DECLARE_SPINLOCKFLAGS(flags)
    struct demand_dma* dma = &sc->demand_dma;
    u_int32_t intcsr;

    SPIN_LOCK_IRQSAVE(dma->spin, flags);
    dma->block[block].used=1;
    dma->active_block=block;
    SPIN_UNLOCK_IRQRESTORE(dma->spin, flags);

    /* clear IRQ */
    plxwritereg(sc, DMACSR0_DMACSR1, 1<<3);

    /* enable IRQ */
    SPIN_LOCK_IRQSAVE(sc->lock_intcsr, flags);
    intcsr=plxreadreg(sc, INTCSR);
    plxwritereg(sc, INTCSR, intcsr|plxirq_dma0);
    SPIN_UNLOCK_IRQRESTORE(sc->lock_intcsr, flags);

    /* restart DMA */
    /*pERROR(sc, "start_dma: dmadpr0=%08x", dma->block[block].dmadpr0|9);*/
    plxwritereg(sc, DMADPR0, dma->block[block].dmadpr0|9);
    plxwritereg(sc, DMACSR0_DMACSR1, 3);
}


/* plxirq_dma0_hook is called in from interrupt! */
static void
plxirq_dma0_hook(struct sis1100_softc *sc)
{
    DECLARE_SPINLOCKFLAGS(flags)
    struct demand_dma* dma = &sc->demand_dma;
    int nextblock, used;

/*
    u_int32_t DMACSR0, d0_bc, ddt_count;

    DMACSR0=plxreadreg(sc, DMACSR0_DMACSR1);
    d0_bc=sis1100readreg(sc, d0_bc);
    ddt_count=plxreadlocal0(sc, 0x10+0x800);
    pERROR(sc, "hook: block=%d DMACSR0=0x%x d0_bc=%d ddt_count=%d",
        dma->active_block, DMACSR0, d0_bc, ddt_count);
*/
    dma->last_block=dma->active_block;
    nextblock=dma->active_block+1;
    if (nextblock>=dma->numblocks)
        nextblock=0;

    SPIN_LOCK_IRQSAVE(dma->spin, flags);
    used=dma->block[nextblock].used;
    if (used)
        dma->is_blocked=1;
    SPIN_UNLOCK_IRQRESTORE(dma->spin, flags);

    if (used) {
        /* we can not do anything from here */
        /*pERROR(sc, "block %d not free", nextblock);*/
    } else {
        /* start DMA for next block */
        start_dma(sc, nextblock);
    }

    /* let irq_thread inform the user process */
    SPIN_LOCK_IRQSAVE(sc->handlercommand.lock, flags);
    sc->handlercommand.command=handlercomm_ddma;
    SPIN_UNLOCK_IRQRESTORE(sc->handlercommand.lock, flags);
    wakeup(&sc->handler_wait);
}

int
sis1100_ddma_start(struct sis1100_softc *sc, struct sis1100_fdata* fd)
{
    struct demand_dma* dma = &sc->demand_dma;
    u_int32_t dmamode, dmadpr0;
    int i;

pERROR(sc, "ddma_start");

    SEM_LOCK(dma->sem);
    /* is an other process using dma? */
    if ((dma->status!=dma_invalid) && (dma->owner!=fd)) {
        SEM_UNLOCK(dma->sem);        
        return EPERM;
    }
    if (dma->status==dma_running) {
        SEM_UNLOCK(dma->sem);        
        return EALREADY;
    }
    if (dma->status!=dma_ready) {
        SEM_UNLOCK(dma->sem);        
        return EINVAL;
    }

    /* declare all blocks as free */
    for (i=0; i<dma->numblocks; i++)
        dma->block[i].used=0;
    /* mark the first block as used */
    dma->block[0].used=1;
    dma->active_block=0;
    dma->is_blocked=0;

    /* prepare PLX */
    dmamode=3     /* bus width 32 bit */
        |(1<<6)   /* enabe TA#/READY# input */
        |(1<<7)   /* enable BTERM# input */
        |(1<<8)   /* enable loacal burst */
        |(1<<9)   /* scatter/gather mode */
        |(1<<10)  /* enabe "done interrupt" */
        |(1<<11)  /* local address is constant */
        |(1<<12)  /* demand mode */
        |(1<<17); /* routing DMA interrupt to PCI bus */
        /*|(1<<14)*/  /* enable DMA EOT# */
        /* EOT stops DMA after the first event! */

    plxwritereg(sc, DMACSR0_DMACSR1, 1<<3); /* clear irq */
    plxwritereg(sc, DMAMODE0, dmamode);
    dmadpr0=dma->block[0].dmadpr0|9;
pERROR(sc, "dma_start: DMADPR0 <-- 0x%08x", dmadpr0);
    plxwritereg(sc, DMADPR0, dmadpr0);

pERROR(sc, "dma_start:set hook to %p", plxirq_dma0_hook);
    sc->plxirq_dma0_hook=plxirq_dma0_hook;

    /* enable dma irq in plx chip */
    sis1100_enable_irq(sc, plxirq_dma0, 0);

    /* clear byte counter */
    sis1100writereg(sc, d0_bc, 0);
    lvd_writeremreg(sc, ddt_counter, 0, 0);

    /* enable remote access */
    sis1100writereg(sc, cr, cr_ready);
    mb_plx();

    /* enable and start dma transfer */
    pDEBUG(sc, "dma_start: Start DMA transfer");
    plxwritereg(sc, DMACSR0_DMACSR1, 3);

    dma->status=dma_running;
    SEM_UNLOCK(dma->sem);

    return 0;
}

int
sis1100_ddma_stop(struct sis1100_softc *sc, struct sis1100_fdata* fd,
        struct sis1100_ddma_stop* d)
{
    struct demand_dma* dma=&sc->demand_dma;

pERROR(sc, "ddma_stop");

    SEM_LOCK(dma->sem);
    /* is an other process using dma? */
    if ((dma->status!=dma_invalid)&&(dma->owner!=fd)){
        SEM_UNLOCK(dma->sem);        
        return EPERM;
    }
    if (dma->status!=dma_running) {
        SEM_UNLOCK(dma->sem);        
        return EINVAL;
    }

    sis1100_disable_irq(sc, plxirq_dma0, 0);
    sc->plxirq_dma0_hook=0;

    /* enable DMA EOT# */
    plxwritereg(sc, DMAMODE0, plxreadreg(sc, DMAMODE0)|(1<<14));
    if (!(plxreadreg(sc, DMACSR0_DMACSR1)&0x10)) {
        int c=0;
        sis1100writereg(sc, sr, sr_abort_dma);
        while (!(plxreadreg(sc, DMACSR0_DMACSR1)&0x10) && (c++<1000000)) {}
        if (!(plxreadreg(sc, DMACSR0_DMACSR1)&0x10)) {
	    pERROR(sc, "dma_stop: DMA NOT STOPED");
            SEM_UNLOCK(dma->sem);        
	    return EIO;
        }
    }
    /* disable remote access */
    sis1100writereg(sc, cr, cr_ready<<16);
    /*
    {
        size_t _bytes;
        _bytes=sis1100readreg(sc, byte_counter);
        pINFO(sc, "dma_stop: byte_count=%u", _bytes);
        if (bytes)
            *bytes=_bytes;
    }
    */

    dma->status=dma_ready;
    SEM_UNLOCK(dma->sem);

    return 0;
}

int
sis1100_ddma_wait(struct sis1100_softc *sc, struct sis1100_fdata* fd,
        int* block)
{
    struct demand_dma* dma=&sc->demand_dma;
    int res;

pERROR(sc, "ddma_wait");

    SEM_LOCK(dma->sem);

    /* are we the owner and DMA is running? */
    if ((dma->status!=dma_running) || (dma->owner!=fd)) {
        SEM_UNLOCK(dma->sem);        
        return EINVAL;
    }

    sc->got_irqs=0;

    if ((plxreadreg(sc, DMACSR0_DMACSR1)&0x10)) {
        goto finished;
    }

#ifdef __NetBSD__
    while (!(res||(sc->got_irqs&(got_dma0)))) {
        res = tsleep(&sc->local_wait, PCATCH, "dma_wait", 10*hz);
    }
#else
    res=wait_event_interruptible (
        sc->local_wait,
        (sc->got_irqs & got_dma0)
    );
#endif
    if (res) {
        SEM_UNLOCK(sc->demand_dma.sem);        
        return EINTR;
    }

finished:

    sis1100_disable_irq(sc, plxirq_dma0, 0);

    /*
    {
        size_t _bytes;
        _bytes=sis1100readreg(sc, byte_counter);
        pDEBUG(sc, "dma_wait: byte_count=%u", _bytes);
        if (bytes)
            *bytes=_bytes;
    }
    */

    dma->status=dma_ready;
    SEM_UNLOCK(dma->sem);

    return 0;
}

int
sis1100_ddma_mark(struct sis1100_softc *sc, struct sis1100_fdata* fd,
        int* block)
{
    DECLARE_SPINLOCKFLAGS(flags)
    struct demand_dma* dma=&sc->demand_dma;
    int blocked, used, i;

    /* are we the owner and DMA is running? */
    if (dma->owner!=fd) {
        pERROR(sc, "ddma_mark: not owner");
        return EPERM;
    }
    if (dma->status!=dma_running) {
        pERROR(sc, "ddma_mark: not running");
        return ESRCH;
    }

    if ((*block<0) || (*block>=dma->numblocks)) {
        pERROR(sc, "ddma_mark: block %d is invalid", *block);
        return EINVAL;
    }

    SPIN_LOCK_IRQSAVE(dma->spin, flags);
    blocked=dma->is_blocked;
    used=dma->block[*block].used;
    if (!used) {
        SPIN_UNLOCK_IRQRESTORE(dma->spin, flags);
        pERROR(sc, "ddma_mark: block %d not used! active_block=%d blocked=%d",
            *block, dma->active_block, blocked);
        for (i=0; i<dma->numblocks; i++)
            pERROR(sc, "block %d: %d", i, dma->block[i].used);
        return EINVAL;
    }
    dma->block[*block].used=0;
    SPIN_UNLOCK_IRQRESTORE(dma->spin, flags);

    if (blocked) {
        int nextblock;
        /* sanity check */
        nextblock=dma->active_block+1;
        if (nextblock>=dma->numblocks)
            nextblock=0;
        if (nextblock!=*block) {
            pERROR(sc, "ddma_mark: marked block=%d, next block=%d",
                *block, nextblock);
            return 0;
        }
        dma->is_blocked=0;
        start_dma(sc, nextblock);
    }

    return 0;
}
